package com.demo.widget.meis.scale;

import com.meis.widget.ResourceTable;
import com.meis.widget.photodrag.PhotoDragRelativeLayout;
import com.meis.widget.utils.DensityUtil;
import com.ryan.ohos.extension.event.DispatchHelper;
import com.ryan.ohos.extension.event.impl.ViewHelper;
import com.ryan.ohos.extension.event.interfaces.View;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentParent;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.global.configuration.Configuration;
import ohos.global.resource.NotExistException;
import ohos.global.resource.Resource;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;

public class ScaleImage extends Component implements Component.EstimateSizeListener, Component.DrawTask, Component.TouchEventListener, View {
    private Context mContext;
    //最大缩放比例
    private static final float MAX_SCALE = 3.0f;
    //中等缩放比例
    private static final float MID_SCALE = 1.75f;
    //最小缩放比例
    /*private static final float MIN_SCALE = 1.0f;*/
    //设置的双击检查时间限制
    private static final long DOUBLE_CLICK_TIME_OUT = 300;
    private static final long DOUBLE_CLICK_MIN_TIME = 40;
    private static final float DOUBLE_POINT_MIN_DISTANCE = 50;
    private ViewHelper impl;
    //imageView的大小
    private Point mComponentSize;
    //图片的大小
    private Point mImageSize;
    //缩放后图片的大小
    private Point mScaleSize = new Point();
    //最初的宽高的缩放比例
    private Point mOriginScale = new Point();
    //imageview中bitmap的xy实时坐标
    private Point mBitmapOriginPoint = new Point();
    //点击的点
    private Point mClickPoint = new Point();
    //上次点击的时间
    private long mLastClickTime = 0;
    //当前缩放的模式
    private ScaleLevel mScaleLevel = ScaleLevel.ORDINARY;
    //临时坐标比例数据
    private Point mTempPoint = new Point();
    //两点之间的距离
    private float mDoublePointDistance = 0;
    //双指缩放时候的中心点
    private Point mDoublePointCenter = new Point();
    //两指缩放的比例
    private float mDoubleFingerScale = 0;
    //上次触碰的手指数量
    private int mLastFingerNum = 0;
    //矩阵
    private Matrix mMatrix;
    //画笔
    private Paint mPaint;
    //要绘制的图片
    private PixelMap mPixelMap;
    //屏幕偏移量，因为有标题栏，而postTranslate的y坐标是相对于屏幕的
    private float mScreenOffset;
    private float mOriginY;
    private PixelMapHolder mPixelMapHolder;
    //移动时的边界值
    private Point mMoveBorderPoint = new Point();
    //屏幕方向
    private int mDirection;

    public ScaleImage(Context context) {
        this(context, null);
    }

    public ScaleImage(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public ScaleImage(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        this.mContext = context;
        init();
    }

    private void init() {
        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
        impl = new ViewHelper(this);
        mMatrix = new Matrix();
        mPaint = new Paint();
        mScreenOffset = DensityUtil.getDimensionPixelSize(mContext, ResourceTable.String_toolbar_height);
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = EstimateSpec.getSize(widthMeasureSpec);
        int height = EstimateSpec.getSize(heightMeasureSpec);
        mComponentSize = new Point(width, height);
        PixelMap pixelMap = getPixelMap();
        if (pixelMap != null) {
            Size size = pixelMap.getImageInfo().size;
            mImageSize = new Point(size.width, size.height);
            showCenter();
        }
        return false;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        canvas.setMatrix(mMatrix);
        canvas.drawPixelMapHolder(mPixelMapHolder, 0, 0, mPaint);
        canvas.resetMatrix();
    }

    public void setPixelMap(int resId) {
        if (resId > 0) {
            mPixelMap = createPixelMap(resId);
            mPixelMapHolder = new PixelMapHolder(mPixelMap);
        }
    }

    private PixelMap createPixelMap(int resId) {
        try {
            Resource resource = getResourceManager().getResource(resId);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            return ImageSource.create(resource, sourceOptions).createPixelmap(decodingOptions);
        } catch (IOException | NotExistException e) {
            e.printStackTrace();
        }
        return null;
    }

    public PixelMap getPixelMap() {
        return mPixelMap;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        return DispatchHelper.dispatch(component, touchEvent);
    }

    private void handlePrimaryPointDown(TouchEvent touchEvent) {
        //手指按下事件
        //记录被点击的点的坐标
        int index = touchEvent.getIndex();
        mClickPoint.modify(touchEvent.getPointerPosition(index).getX(), touchEvent.getPointerPosition(index).getY());
        //判断屏幕上此时被按住的点的个数，当前屏幕只有一个点被点击的时候触发
        if (touchEvent.getPointerCount() == 1) {
            //设置一个点击的间隔时长，来判断是不是双击
            long time = touchEvent.getOccurredTime() - mLastClickTime;
            if (DOUBLE_CLICK_MIN_TIME <= time && time <= DOUBLE_CLICK_TIME_OUT) {
                switch (mScaleLevel) {
                    case ORDINARY:
                        //如果图片此时缩放模式是普通模式，就触发双击放大
                        handleDoubleClickScale(MID_SCALE);
                        mScaleLevel = ScaleLevel.MID;
                        setIntercept(false);
                        break;
                    case MID:
                        //继续放大到最大
                        handleDoubleClickScale(MAX_SCALE);
                        mScaleLevel = ScaleLevel.MAX;
                        setIntercept(false);
                        break;
                    case MAX:
                        //还原
                        showCenter();
                        mScaleLevel = ScaleLevel.ORDINARY;
                        setIntercept(true);
                        break;
                }
            } else {
                mLastClickTime = touchEvent.getOccurredTime();
            }
        }
    }

    private void setIntercept(boolean disable) {
        ComponentParent componentParent = getComponentParent();
        if (componentParent instanceof PhotoDragRelativeLayout) {
            ((PhotoDragRelativeLayout) componentParent).setIntercept(disable);
        }
    }

    private void handleDoubleClickScale(float scale) {
        // 重要，当前已处于MID缩放状态，在即将缩放到MAX时，需要对bitmapOriginPoint重新从Matrix获取一下值
        if (scale == MAX_SCALE) {
            getPixelMapOffset();
        }
        //分别记录被点击的点到图片左上角x,y轴的距离与图片x,y轴边长的比例，方便在进行缩放后，算出这个点对应的坐标点
        mTempPoint.modify(
                (mClickPoint.getPointX() - mBitmapOriginPoint.getPointX()) / mScaleSize.getPointX(),
                (mClickPoint.getPointY() - mBitmapOriginPoint.getPointY()) / mScaleSize.getPointY());
        float scaleX = mOriginScale.getPointX() * scale;
        float scaleY = mOriginScale.getPointY() * scale;
        doubleClickScaleAndTranslate(scaleX, scaleY);
        mDoubleFingerScale = scaleX;
    }

    private void doubleClickScaleAndTranslate(float scaleX, float scaleY) {
        //进行缩放
        scaleImage(scaleX, scaleY);
        //获取缩放后图片左上角的xy坐标
        getPixelMapOffset();
        //平移图片，使得被点击的点的位置不变。这里是计算缩放后被点击的xy坐标，与原始点击的位置的xy坐标值，计算出差值，然后做平移动作
        switch (mDirection) {
            case Configuration.DIRECTION_VERTICAL:
                translationImage(
                        mClickPoint.getPointX() - (mBitmapOriginPoint.getPointX()
                                + mTempPoint.getPointX() * mScaleSize.getPointX()),
                        (mComponentSize.getPointY() - mScaleSize.getPointY()) / 2 + mScreenOffset);
                break;
            case Configuration.DIRECTION_HORIZONTAL:
                translationImage(
                        (mComponentSize.getPointX() - mScaleSize.getPointX()) / 2,
                        mClickPoint.getPointY() - (mBitmapOriginPoint.getPointY()
                                + mTempPoint.getPointY() * mScaleSize.getPointY()) + mScreenOffset);
                break;
            default:break;
        }
    }

    private void handlePointMove(TouchEvent touchEvent) {
        //移动
        if (mScaleLevel != ScaleLevel.ORDINARY) {
            //如果是多指，计算中心点为假设的点击的点
            float currentX = 0;
            float currentY = 0;
            //获取此时屏幕上被触碰的点有多少个
            int pointCount = touchEvent.getPointerCount();
            //计算出中间点所在的坐标
            for (int i = 0; i < pointCount; i++) {
                currentX += touchEvent.getPointerPosition(i).getX();
                currentY += touchEvent.getPointerPosition(i).getY();
            }
            currentX /= pointCount;
            currentY /= pointCount;
            //当屏幕被触碰的点的数量变化时，将最新算出来的中心点看作是被点击的点
            if (mLastFingerNum != touchEvent.getPointerCount()) {
                mClickPoint.modify(currentX, currentY);
                mLastFingerNum = touchEvent.getPointerCount();
            }
            //将移动手指时，实时计算出来的中心点坐标，减去被点击点的坐标就得到了需要移动的距离
            float moveX = currentX - mClickPoint.getPointX();
            float moveY = currentY - mClickPoint.getPointY();
            //计算边界，使得不能已出边界，但是如果是双指缩放时移动，因为存在缩放效果，所以此时的边界判断无效
            moveBorderDistance(moveX, moveY);
            //处理移动图片的事件
            translationImage(mMoveBorderPoint.getPointX(), mMoveBorderPoint.getPointY());
            mClickPoint.modify(currentX, currentY);
        }
        realScale(touchEvent);
    }

    private void realScale(TouchEvent touchEvent) {
        //缩放
        //判断当前是两个手指接触到屏幕才处理缩放事件
        if (touchEvent.getPointerCount() == 2) {
            //如果此时缩放后的大小，大于等于了设置的最大缩放的大小，就不处理
            if ((mScaleSize.getPointX() / mImageSize.getPointX() >= mOriginScale.getPointX() * MAX_SCALE
                    || mScaleSize.getPointY() / mImageSize.getPointY() >= mOriginScale.getPointY() * MAX_SCALE)
                    && getDoubleFingerDistance(touchEvent) - mDoublePointDistance > 0) {
                return;
            }
            //这里设置当双指缩放的的距离变化量大于50，并且当前不是在双指缩放状态下，就计算中心点，等一些操作
            if (Math.abs(getDoubleFingerDistance(touchEvent) - mDoublePointDistance)
                    > DOUBLE_POINT_MIN_DISTANCE && mScaleLevel != ScaleLevel.TWO_FINGER_SCALE) {
                //计算两个手指之间的中心点，当作放大的中心点
                mDoublePointCenter.modify(
                        (touchEvent.getPointerPosition(0).getX() + touchEvent.getPointerPosition(1).getX()) / 2,
                        (touchEvent.getPointerPosition(0).getY() + touchEvent.getPointerPosition(1).getY()) / 2);
                //将双指的中心点就假设为点击的点
                mClickPoint.modify(mDoublePointCenter);
                //下面就和双击放大基本一样
                getPixelMapOffset();
                //分别记录被点击的点到图片左上角x,y轴的距离与图片x,y轴边长的比例，方便在进行缩放后，算出这个点对应的坐标点
                mTempPoint.modify(
                        (mClickPoint.getPointX() - mBitmapOriginPoint.getPointX()) / mScaleSize.getPointX(),
                        (mClickPoint.getPointY() - mBitmapOriginPoint.getPointY()) / mScaleSize.getPointY());
                //设置进入双指缩放状态
                mScaleLevel = ScaleLevel.TWO_FINGER_SCALE;
            }
            //如果已经进入双指缩放状态，就直接计算缩放的比例，并进行位移
            if (mScaleLevel == ScaleLevel.TWO_FINGER_SCALE) {
                //用当前的缩放比例与此时双指间距离的缩放比例相乘，就得到对应的图片应该缩放的比例
                float scale = mDoubleFingerScale * getDoubleFingerDistance(touchEvent) / mDoublePointDistance;
                //这里也是和双击放大时一样的
                doubleClickScaleAndTranslate(scale, scale);
            }
        }
    }

    /**
     * 设置图片居中等比显示
     */
    private void showCenter() {
        float scaleX = mComponentSize.getPointX() / mImageSize.getPointX();
        float scaleY = mComponentSize.getPointY() / mImageSize.getPointY();
        float scale = Math.min(scaleX, scaleY);
        scaleImage(scale, scale);
        //移动图片，并保存最初的图片左上角（即原点）所在坐标
        if (scaleX < scaleY) {
            translationImage(0, (mComponentSize.getPointY() - mScaleSize.getPointY()) / 2 + mScreenOffset);
            mBitmapOriginPoint.modify(0,
                    (mComponentSize.getPointY() - mScaleSize.getPointY()) / 2 + mScreenOffset);
        } else {
            translationImage((mComponentSize.getPointX() - mScaleSize.getPointX()) / 2, mScreenOffset);
            mBitmapOriginPoint.modify((mComponentSize.getPointX() - mScaleSize.getPointX()) / 2, mScreenOffset);
        }
        //保存下最初的缩放比例
        mOriginScale.modify(scale, scale);
        mDoubleFingerScale = scale;
    }

    private void scaleImage(float scaleX, float scaleY) {
        mMatrix.setScale(scaleX, scaleY);
        mScaleSize.modify(scaleX * mImageSize.getPointX(), scaleY * mImageSize.getPointY());
        // 每次scale之后必然会translate，而translate里面会invalidate，此处不需要invalidate，减少绘制事件
        mOriginY = (mComponentSize.getPointY() - mScaleSize.getPointY()) / 2 + mScreenOffset;
    }

    /**
     * 对图片进行x和y轴方向的平移
     *
     * @param dx x轴平移量
     * @param dy y轴平移量
     */
    private void translationImage(float dx, float dy) {
        mMatrix.postTranslate(dx, dy);
        invalidate();
        if (mScaleLevel == ScaleLevel.ORDINARY) {
            setIntercept(true);
        } else {
            float[] data = getData();
            boolean nearLeft = (data[2] == 0F);
            boolean nearRight = ((int) (data[2] + mScaleSize.getPointX()) == (int) mComponentSize.getPointX());
            setIntercept(nearLeft || nearRight);
        }
    }

    private float[] getData() {
        return mMatrix.getData();
    }

    /**
     * 防止移动图片超过边界，计算边界情况
     *
     * @param moveX X轴移动距离
     * @param moveY Y轴移动距离
     */
    private void moveBorderDistance(float moveX, float moveY) {
        //计算bitmap的左上角坐标
        getPixelMapOffset();
        if (moveY > 0) {
            //向下滑
            if (mBitmapOriginPoint.getPointY() < mScreenOffset) {
                // 此时Y轴坐标已超出可视区域，当要移动的距离大于或等于可以移动的距离，则赋值，否则移动距离不变，下面的向上滑同理
                float canMoveDistance = mScreenOffset - mBitmapOriginPoint.getPointY();
                if (moveY >= canMoveDistance) {
                    moveY = canMoveDistance;
                }
            } else {
                moveY = 0;
            }
        } else if (moveY < 0) {
            //向上滑
            //计算bitmap的右下角坐标
            float bitmapRightBottomY = mBitmapOriginPoint.getPointY() + mScaleSize.getPointY();
            if (bitmapRightBottomY > (mComponentSize.getPointY() + mScreenOffset)) {
                float canMoveDistance = (mComponentSize.getPointY() + mScreenOffset) - bitmapRightBottomY;
                if (moveY <= canMoveDistance) {
                    moveY = canMoveDistance;
                }
            } else {
                moveY = 0;
            }
        }
        if (moveX > 0) {
            //向右滑
            if (mBitmapOriginPoint.getPointX() + moveX > 0) {
                if (mBitmapOriginPoint.getPointX() < 0) {
                    moveX = -mBitmapOriginPoint.getPointX();
                } else {
                    moveX = 0;
                }
            }
        } else if (moveX < 0) {
            //向左滑
            //计算bitmap的右下角坐标
            float bitmapRightBottomX = mBitmapOriginPoint.getPointX() + mScaleSize.getPointX();
            if (bitmapRightBottomX + moveX < mComponentSize.getPointX()) {
                if (bitmapRightBottomX > mComponentSize.getPointX()) {
                    moveX = -(bitmapRightBottomX - mComponentSize.getPointX());
                } else {
                    moveX = 0;
                }
            }
        }

        mMoveBorderPoint.modify(moveX, moveY);
    }

    /**
     * 获取view中bitmap的坐标点
     */
    private void getPixelMapOffset() {
        float[] value = getData();
        mBitmapOriginPoint.modify(value[2], value[5]);
    }

    /**
     * 计算两个手指间的距离
     *
     * @param touchEvent TouchEvent
     * @return float Distance
     */
    private float getDoubleFingerDistance(TouchEvent touchEvent) {
        float x = touchEvent.getPointerPosition(0).getX() - touchEvent.getPointerPosition(1).getX();
        float y = touchEvent.getPointerPosition(0).getY() - touchEvent.getPointerPosition(1).getY();
        return (float) Math.sqrt(x * x + y * y);
    }

    public Matrix getMatrix() {
        return mMatrix;
    }

    public float getOriginY() {
        return mOriginY;
    }

    @Override
    public boolean dispatchTouchEvent(TouchEvent touchEvent) {
        return impl.dispatchTouchEvent(touchEvent);
    }

    @Override
    public boolean onTouchEvent(TouchEvent touchEvent) {
        // 获取触摸点坐标，貌似那两个方法都有问题，所以设置一个屏幕Y轴上的偏移量，为标题栏高度
        touchEvent.setScreenOffset(0, mScreenOffset);
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                handlePrimaryPointDown(touchEvent);
                break;
            case TouchEvent.OTHER_POINT_DOWN:
                //屏幕上已经有一个点按住 再按下一点时触发该事件
                //计算最初的两个手指之间的距离
                mDoublePointDistance = getDoubleFingerDistance(touchEvent);
                break;
            case TouchEvent.OTHER_POINT_UP:
                //屏幕上已经有两个点按住 再松开一点时触发该事件
                //当有一个手指离开屏幕后，就修改状态，这样如果双击屏幕就能恢复到初始大小
                mScaleLevel = ScaleLevel.MID;
                setIntercept(false);
                //记录此时的双指缩放比例
                mDoubleFingerScale = mScaleSize.getPointX() / mImageSize.getPointX();
                //记录此时屏幕触碰的点的数量
                mLastFingerNum = 1;
                //判断缩放后的比例，如果小于最初的那个比例，就恢复到最初的大小
                if (mScaleSize.getPointX() < mComponentSize.getPointX()
                        && mScaleSize.getPointY() < mComponentSize.getPointY()) {
                    mScaleLevel = ScaleLevel.ORDINARY;
                    setIntercept(true);
                    showCenter();
                }
                break;
            case TouchEvent.POINT_MOVE:
                handlePointMove(touchEvent);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                //手指松开时触发事件
                mLastFingerNum = 0;
                break;
        }
        return true;
    }

    @Override
    public boolean isConsumed() {
        return impl.isConsumed();
    }

    public void setDirection(int direction) {
        mDirection = direction;
    }

    private enum ScaleLevel {
        ORDINARY, MID, MAX, TWO_FINGER_SCALE
    }
}
